#!/usr/bin/python2
# -*- coding: utf-8 -*-

from PySide import QtGui, QtSvg, QtCore
from ui import Ui_MainWindow
from widgets import dialogs, Overlay, SvgViewer, \
        MolViewer, MSViewer, SdfForm
        #ChemWriter
from utils import Settings, SETTINGS_FILE, myproduct, \
        tr, PythonHighlighter, zipickle
from models import ListModel_1, ListModel_2, MyTableView, \
        TableModel_1, DataStoreRole
from spectra import MS, parse_mzXML, extract_MSn
from mol import Mol2pic, Skel, Mol, sub_filter, filter_sdf
from db import Pattern, PeriphGroup, AND, OR, Gen_confirm_query
import time, os, sys, types, copy, json, types, random, re
from plugins import Plugins
from version import Version

def Snapshot(obj, filename = 'Snapshot.png'):
    ext = os.path.splitext(filename)[1]
    if ext:
        ext = ext[1:].lower()
    if filename:
        srcsize = obj.size()
        srcsize.setHeight(srcsize.height() + 10)
        srcsize.setWidth(srcsize.width() + 10)

        if ext in ('pdf', 'ps'):
            device = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
            device.setOutputFileName(filename)
            if ext == 'pdf':
                device.setOutputFormat(QtGui.QPrinter.PdfFormat)
            else:
                # ext == 'ps'
                device.setOutputFormat(QtGui.QPrinter.PostScriptFormat)
        elif ext == 'svg':
            device = QtSvg.QSvgGenerator()
            device.setFileName(filename)
            device.setSize(srcsize)
        else:
            device = QtGui.QPixmap(srcsize)
            device.fill(QtCore.Qt.white)

        painter = QtGui.QPainter()
        if painter.begin(device):
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            obj.render(painter, QtCore.QPoint())
            painter.end()
            if hasattr(device , 'save'):
                device.save(filename)
        else:
            QtGui.QtGui.QMessageBox.warning(
                        parent,
                        obj.tr('Warning'),
                        obj.tr('Unable initialize painting device.'))

class MyThread(QtCore.QThread):
    def __init__(self, parent = None):
        QtCore.QThread.__init__(self, parent)
        self.callback = None

    def run(self):
        if self.callback:
            self.callback()

class MolConfirmer(QtCore.QThread):
    sigQueryFinished = QtCore.Signal(object)
    sigQueryFailed = QtCore.Signal(object)
    def __init__(self, query_obj = None, query = '', parent = None):
        QtCore.QThread.__init__(self, parent)
        self.query_obj = query_obj
        self.query = query

    def set_query_obj(self, query_obj):
        self.query_obj = query_obj

    def set_query(self, query):
        self.query = query

    def run(self):
        #print self.query_obj, self.querys
        if self.query_obj and self.query != '':
            try:
                #print post_data_dic
                results = self.query_obj.search(self.query, True)
                self.sigQueryFinished.emit(results)
                return
            except Exception, e:
                sys.stderr.write(str(e))
                self.sigQueryFailed.emit(tr('Not connected to server or no querys.'))
                return

class Window(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self, parent = None):
        QtGui.QMainWindow.__init__(self, parent)
        self.setupUi(self)

        self.reg = re.compile(r'J[a-z]')

        self.setWindowTitle('%s - %s' %(tr('SmartMass'), Version))

        self.registered_plugins = {}

        self.gen_confirm_query = Gen_confirm_query

        # MS viewer for MS displaying
        self.ms_viewer = MSViewer('', self)
        self.verticalLayout_9.addWidget(self.ms_viewer)
        self.ms_viewer.hide_btns(True)

        # SDF viewer
        self.sdf_viewer = SdfForm(load_on_init = False, parent = self)
        self.verticalLayout_8.addWidget(self.sdf_viewer)
        # add a blank page
        self.sdf_viewer.add_view()
        self.sdf_viewer.set_data('')

        # update settings
        self.settings = None
        self.update_settings()

        # set up plugins
        self._install_plugins()

        # molconfirmer
        self.molconfirmer = MolConfirmer()
        self.molconfirmer.set_query_obj(self.query_obj)

        # set the adduct ions in comboBox
        self.positive_ions = self.settings.positive_ions
        self.negative_ions = self.settings.negative_ions
        self.ion_list = self.positive_ions
        self.polarity = '+'
        self._set_adduct_ions()

        # line edit with number validator
        self.number_line_edits = [
                self.lineEdit_2,
                self.lineEdit_3,
                self.lineEdit_6,
                ]
        for le in self.number_line_edits:
            le.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(r'[1-9][0-9\.]*'), le))

        # set resolution comboBox
        self.resolutions = [
                (tr('HRMS'), 1),
                (tr('LRMS'), 0),
                ]
        self.comboBox_3.addItems([i[0] for i in self.resolutions])
        self.ms_resolution = self.resolutions[0][1]

        # init error dialog and file_selector objects
        self.error_dialog = dialogs.MyErrorDialog()
        self.file_selector = QtGui.QFileDialog(self)
        self.last_ms_file_dir = '.'
        self.last_mol_file_dir = '.'
        self.last_mzxml_file_dir = '.'
        self.last_mzlst_file_dir = '.'

        self.orig_line_edit_text = ''

        # molviewer or moleditor to display/edit mols
        #self.mol_editor = ChemWriter('qrc:/webrc/chemwriter.html')
        self.mol_editor = MolViewer('', self)
        self.horizontalLayout_10.addWidget(self.mol_editor)

        self.listView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.list_model_idx = None

        # init pattern/group listView
        self.pattern_list_model = ListModel_1([])
        self.group_list_model = ListModel_1([])
        self._update_list_models()

        # MS2 listView
        self.ms_list = []
        self.ms2_list_model = ListModel_2([])
        self.listView_2.setModel(self.ms2_list_model)
        if self.ms2_list_model.listdata:
            self.listView_2.show()
            self.pushButton_16.show()
        else:
            self.listView_2.hide()
            self.pushButton_16.hide()

        # set the pattern types in comboBox_2
        self.pattern_types = [
            (tr('Frag. pattern'), self.pattern_list_model),
            (tr('Periph. Group'), self.group_list_model)]
        self._set_pattern_types()
        self._set_list_view(self.pattern_list_model)

        # set tableView to display the analysis outputs
        self.tableView = MyTableView()
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setVerticalStretch(1)
        self.tableView.setSizePolicy(sizePolicy)
        self.tableView.setMinimumSize(QtCore.QSize(600, 400))
        self.tableView.setMaximumSize(QtCore.QSize(800, 1677215))
        self.horizontalLayout_12.addWidget(self.tableView)
        self.table_header = [
                    tr('Number'),
                    tr('Sturcture'),
                    tr('Note'),
                    tr('Action'),
                    tr('Action')]
        self.table_model = TableModel_1([], self.table_header)
        self.tableView.column_width = (1, 155)
        self.tableView.row_height = (0, 150)
        self.tableView.icon_size = QtCore.QSize(145, 145)
        self.tableView.setModel(self.table_model)
        if self.table_model.arraydata:
            self._reset_table_model()
        self.table_overlay = Overlay(self.tableView)
        self.table_overlay.hide()

        # set the pattern listView check state
        self.pattern_list_view_is_checked_all = QtCore.Qt.Unchecked
        self.group_list_view_is_checked_all = QtCore.Qt.Unchecked
        self.pushButton_11.setText(tr('Select all'))
        self.pushButton_14.setText(tr('Select all'))

        self.pattern_plain_text_edits = {
            'pre_defs': self.plainTextEdit_5,
            'diagnostic_pattern': self.plainTextEdit_2,
            #'peak_pattern': self.plainTextEdit_3,
            'result_pattern': self.plainTextEdit_4
            }

        # set code highlight
        for e in self.pattern_plain_text_edits.values():
            e.setStyleSheet('''
                    background-color: #D2D2D2;font: 10pt "Consolas";
                    ''')
            PythonHighlighter(e.document())

        # hide some buttons
        self._on_combobox_2_index_changed(0)

        # SvgViewers to view enlarged mol graphics
        self.svs = []

        self._connect_signals()


    def _connect_signals(self):
        # File > Import mz-intensity file
        self.action.triggered.connect(self._on_import_ms_file)
        # File > Close
        self.action_2.triggered.connect(self.close)
        # Edit > Edit rules
        self.action_3.triggered.connect( lambda :self.tabWidget.setCurrentIndex(1) )
        # Help > Document
        self.action_4.triggered.connect(self._on_document)
        # Snapshot
        # self.action_4.triggered.connect(lambda : Snapshot(self))
        # Help > About
        self.action_5.triggered.connect(self._on_about)
        # Edit > Analyze data
        self.action_6.triggered.connect( lambda :self.tabWidget.setCurrentIndex(0) )
        # File > Open mzXML file
        self.action_7.triggered.connect(self._on_open_mzxml_file)
        # File > Load mz list file
        self.action_8.triggered.connect(self._on_load_mzlst_file)
        # File > Dump mz list file
        self.action_9.triggered.connect(self._on_dump_mzlst_file)
        # Show toolbar
        self.actionShowToolBar.triggered.connect( lambda :self.toolBar.show() )
        # Hide toolbar
        self.actionHideToolBar.triggered.connect( lambda :self.toolBar.hide() )
        self.listView.clicked.connect(self._set_pattern_or_group_info)
        # set msn info
        self.listView_2.clicked.connect(self._set_ms_info)
        # Double click in results table (save MOL)
        self.tableView.doubleClicked.connect(self._on_tableView_clicked)
        # test
        #self.pattern_list_model.dataChanged.connect(self.test)
        # Analysis, set busy overlay and analyze in background
        self.pushButton.clicked.connect(self._analyze_ms)
        # Reset
        self.pushButton_2.clicked.connect(self._up_date_ms_viewer)
        # Undo
        self.pushButton_3.clicked.connect(self._up_date_ms_viewer)
        # Redo
        self.pushButton_4.clicked.connect(self._up_date_ms_viewer)
        # import MS data (in txt file)
        self.pushButton_5.clicked.connect(self._on_import_ms_file)
        # export MS data (in txt file)
        self.pushButton_6.clicked.connect(self._on_export_ms_file)
        # updata selected rule
        self.pushButton_7.clicked.connect(self._db_update_selected)
        # add new rule
        self.pushButton_8.clicked.connect(self._db_add_new)
        # add periphral group
        self.pushButton_9.clicked.connect(self._db_add_new)
        # delete selected
        self.pushButton_10.clicked.connect(self._db_del_selected)
        #self.pushButton_10.clicked.connect(self._on_list_view_del_selected)
        # listView check all
        self.pushButton_11.clicked.connect(self._pattern_list_view_check_or_uncheck_all)
        # update MS viewer
        self.pushButton_12.clicked.connect(self._up_date_ms_viewer)
        # update selected periphral group
        self.pushButton_13.clicked.connect(self._db_update_selected)
        # uncheck/check all
        self.pushButton_14.clicked.connect(self._group_list_view_check_or_uncheck_all)
        # clear pattern and group info
        self.pushButton_15.clicked.connect(self._clear_pattern_or_group_info)
        # show/hide mzlst listview_2
        self.pushButton_16.clicked.connect(self._hide_show_listview_2)
        # set confirm results in tableView
        self.molconfirmer.sigQueryFinished.connect(self._sdf_viewer_set_confirm_results)
        # show error dialog when query failed
        self.molconfirmer.sigQueryFailed.connect(self._show_error)
        # listView item clicked, set the pattern plain text edits
        # set the adduct ion list
        self.comboBox.currentIndexChanged.connect(self._on_combobox_index_changed)
        # set rule type
        self.comboBox_2.currentIndexChanged.connect(self._on_combobox_2_index_changed)
        # set the resolution of your MS
        self.comboBox_3.currentIndexChanged.connect(self._update_ms_resolution)
        # radioButton
        self.radioButton.clicked.connect(self._set_adduct_ions)
        self.radioButton_2.clicked.connect(self._set_adduct_ions)
        # auto calc mol exact mass and fill the line edit of exact mass of periph info
        self.mol_editor.sigMolChanged.connect(self._set_periph_emass)

    def resizeEvent(self, event):
        self.table_overlay.resize(self.tableView.size())
        event.accept()

    def update_settings(self):
        self.settings = Settings(SETTINGS_FILE).get_settings()
        try:
            # set query_obj
            self.query_obj = None
            try:
                _temp = __import__('db.%s' %self.settings.search_engine, globals(), locals(), ['Query'], -1)
                Query = _temp.Query
                self.query_obj = Query()
            except:
                pass

            self.ms_viewer.set_iso_width(self.settings.iso_width)
            self.ms_viewer.set_deviation(self.settings.deviation)
            self.ms_viewer.set_noise_level(self.settings.noise_level)
            self.positive_ions = self.settings.positive_ions
            self.negative_ions = self.settings.positive_ions
            self._reset_plugins()

        except Exception, e:
            self._show_error(tr('Failed to update settings: %s.') %str(e))

    def _on_document(self):
        doc_dlg = dialogs.MessageDialog()
        doc_dlg.setWindowTitle(tr('Document'))
        doc_dlg.setText('TODO')
        doc_dlg.exec_()

    def _on_about(self):
        about_dlg = dialogs.MessageDialog()
        about_dlg.setWindowTitle(tr('About'))
        about_dlg.setText(
                tr('''SmartMass: a fragmentation pattern based software for structure elucidation
of natural products using tandem mass spectrometry
        Version: %s
        Licence: GPLv3
        Project home: http://code.google.com/p/smartmass
        Copyright: xiooli <xioooli[at]gmail.com> 2012-2013''') %Version)
        about_dlg.exec_()

    def _show_error(self, msg):
        self.error_dialog.setText(msg)
        self.error_dialog.exec_()
        self.table_overlay.hide()

    def _get_data_for_plugins(self):
        try:
            return {
                    'table_model': self.table_model,
                    'list_model': self.pattern_list_model,
                    'ms_analysis_rule': self._get_ms_analysis_rule(False),
                    'periph_group_info': self._get_periph_group_info(False),
                    'pattern_db_obj': Pattern,
                    'perhph_db_obj': PeriphGroup,
                    'polarity': self.polarity,
                    'ms_obj': self.ms_viewer.ms_obj,
                    'resolution': self.ms_resolution,
                    'adduct': self._get_adduct(),
                    'settings_file': SETTINGS_FILE,
                    'plugins': Plugins,
                    }
        except:
            return {}

    def _init_plugin(self, plugin):
        if plugin['type'] != 'ENHANCER':
            return

        if not self.registered_plugins.has_key(plugin['name']):
            self.registered_plugins[plugin['name']] = {'actions': [], 'widgets': []}

        actions = []
        widgets = []

        try:
            plugin['class'] = plugin['class'](parent = self)
        except:
            pass
        plugin['class'].collector = self._get_data_for_plugins
        plugin['class'].parent = self

        if plugin.has_key('widgets') and plugin['widgets'] != []:
            for w in plugin['widgets']:
                try:
                    if w.has_key('widget_text'):
                        widget_text = w['widget_text']
                    else:
                        widget_text = plugin['name']
                    tab_idx = self.tabWidget.addTab(w['widget'], widget_text)
                    widgets.append((w['widget'], widget_text))
                except:
                    pass

        plugin_dict = {
                'menus': plugin['menus'],
                'buttons': plugin['buttons'],
                }

        for k, v in plugin_dict.items():
            for b in v:
                action = QtGui.QAction(self)
                txt = ''
                if b.has_key('text') and b['text'] != '':
                    try:
                        action.setText(tr(str(b['text'])))
                    except:
                        action.setText(b['text'])
                if k == 'menus':
                    self.menu_P.addAction(action)
                    actions.append(action)
                elif k == 'buttons':
                    self.toolBar.addAction(action)
                    actions.append(action)
                if b.has_key('icon') and b['icon'] and os.path.exists(b['icon']):
                    action.setIcon(QtGui.QIcon(b['icon']))
                if b.has_key('icon_size') and b['icon_size']:
                    try:
                        w, h = b['icon_size']
                        action.setIconSize(QtCore.QSize(w, h))
                    except: pass
                if b.has_key('tool_tip'):
                    try:
                        action.setToolTip(tr(str(b['tool_tip'])))
                    except:
                        action.setToolTip(b['tool_tip'])
                if b.has_key('trigger') and b['trigger']:
                    try:
                        action.triggered.connect(plugin['class'].functions.get(b['trigger']))
                    except: pass
        self.registered_plugins[plugin['name']]['actions'] += actions
        self.registered_plugins[plugin['name']]['widgets'] += widgets

    def _install_plugins(self):
        '''install plugins'''
        show_toolbar = False

        for name, plugin in Plugins.items():
            if not name in self.settings.deactivated_plugins:
                if plugin.has_key('buttons') and plugin['buttons'] != []:
                    show_toolbar = True
                self._install_plugin(plugin)

        if show_toolbar:
            self.toolBar.show()
        else:
            self.toolBar.hide()

    def _install_plugin(self, plugin):
        '''install plugin'''
        cls = plugin['class']
        attrs = dir(cls)

        plugin.update({
                'menus': [],
                'buttons': [],
                'widgets': [],
                })

        for key, val in {
                'menus': 'Menus',
                'buttons': 'Buttons',
                'widgets': 'Widgets'}.items():
            if val in attrs and type(cls.__dict__.get(val)) == types.ListType:
                plugin[key] = cls.__dict__.get(val)

        self._init_plugin(plugin)

    def _reset_plugins(self):
        for p_name, plugin in Plugins.items():
            if p_name in self.settings.deactivated_plugins:
                if self.registered_plugins.has_key(p_name):
                    for a in self.registered_plugins[p_name]['actions']:
                        a.setVisible(False)
                    for w in self.registered_plugins[p_name]['widgets']:
                        try:
                            self.tabWidget.removeTab(self.tabWidget.indexOf(w[0]))
                        except:
                            pass
            else:
                if self.registered_plugins.has_key(p_name):
                    for a in self.registered_plugins[p_name]['actions']:
                        a.setVisible(True)
                    for w in self.registered_plugins[p_name]['widgets']:
                        try:
                            self.tabWidget.addTab(w[0], w[1])
                        except:
                            pass
                else:
                     self._install_plugin(plugin)

    def _hide_show_listview_2(self):
        if self.listView_2.isVisible():
            self.listView_2.hide()
            self.pushButton_16.setText('>')
        else:
            self.pushButton_16.setText('<')
            self.listView_2.show()

    def _pattern_list_view_check_or_uncheck_all(self):
        if self.pattern_list_view_is_checked_all == QtCore.Qt.Checked:
            self.pattern_list_view_is_checked_all = QtCore.Qt.Unchecked
            self.pushButton_11.setText(tr('Select all'))
        else:
            self.pattern_list_view_is_checked_all = QtCore.Qt.Checked
            self.pushButton_11.setText(tr('Unselect all'))
        for row in range(self.pattern_list_model.rowCount()):
            self.pattern_list_model.setChecked(row, self.pattern_list_view_is_checked_all)

    def _group_list_view_check_or_uncheck_all(self):
        if self.group_list_view_is_checked_all == QtCore.Qt.Checked:
            self.group_list_view_is_checked_all = QtCore.Qt.Unchecked
            self.pushButton_14.setText(tr('Select all'))
        else:
            self.group_list_view_is_checked_all = QtCore.Qt.Checked
            self.pushButton_14.setText(tr('Unselect all'))
        for row in range(self.group_list_model.rowCount()):
            self.group_list_model.setChecked(row, self.group_list_view_is_checked_all)

    def _set_list_view(self, model):
        self.listView.setModel(model)

    def _reset_table_model(self):
        self.table_model.reset()
        self.tableView.setUi()

    def _get_ms_analysis_rule(self, show_dlg = True):
        rule = ''
        pre_defs = self.plainTextEdit_5.toPlainText().encode('utf8')
        diagnostic_pattern = self.plainTextEdit_2.toPlainText().encode('utf8')
        #peak_pattern = self.plainTextEdit_3.toPlainText().encode('utf8')
        result_pattern = self.plainTextEdit_4.toPlainText().encode('utf8')
        name = self.lineEdit_4.text().encode('utf8')
        minmz = self.lineEdit_3.text().encode('utf8')
        polarity = '+'
        if self.radioButton_3.isChecked():
            polarity = '+'
        elif self.radioButton_4.isChecked():
            polarity = '-'
        mol = self.mol_editor.get_mol()
        if diagnostic_pattern and result_pattern and name and minmz and mol:
            rule = json.dumps({
                'pre_defs': pre_defs,
                'diagnostic_pattern': diagnostic_pattern,
                #'peak_pattern': peak_pattern,
                'result_pattern': result_pattern
                })
            return {'name': name,
                    'mol': mol,
                    'rule': rule,
                    'polarity': polarity,
                    'minmz': minmz}
        else:
            if show_dlg:
                self._show_error(tr('Please fill all items.'))
            return None

    def _get_periph_group_info(self, show_dlg = True):
        name = self.lineEdit_5.text().encode('utf8')
        mol = self.mol_editor.get_mol()
        exact_mass = self.lineEdit_6.text().encode('utf8')
        if name and mol and exact_mass:
            return {'name': name, 'mol': mol, 'exact_mass': exact_mass}
        else:
            if show_dlg:
                self._show_error(tr('Please fill all items.'))
            return None

    def _update_db(self, mode = 0, id = None):
        # type == 0: add new; type == 1: update selected; type == 2: delete selected
        if self.comboBox_2.currentIndex() == 0:
            rlt = self._get_ms_analysis_rule()
            if rlt:
                mol = rlt['mol']
                if mode == 0:
                    Pattern(name = rlt['name'],
                            minmz = float(rlt['minmz']),
                            rule = rlt['rule'],
                            polarity = rlt['polarity'],
                            mol = mol)
                elif mode == 1 and id and type(id) == types.IntType:
                    Pattern.get(id).set(name = rlt['name'],
                            minmz = float(rlt['minmz']),
                            rule = rlt['rule'],
                            polarity = rlt['polarity'],
                            mol = mol)
                elif mode == 2 and id and type(id) == types.IntType:
                    Pattern.delete(id)

                self._update_list_models()
                self._set_list_view(self.pattern_list_model)

        elif self.comboBox_2.currentIndex() == 1:
            rlt = self._get_periph_group_info()
            if rlt:
                mol = rlt['mol']
                if mode == 0:
                    PeriphGroup(name = rlt['name'],
                            exact_mass = float(rlt['exact_mass']),
                            mol = mol)
                elif mode == 1 and id and type(id) == types.IntType:
                    PeriphGroup.get(id).set(name = rlt['name'],
                            exact_mass = float(rlt['exact_mass']),
                            mol = mol)
                elif mode == 2 and id and type(id) == types.IntType:
                    PeriphGroup.delete(id)

                self._update_list_models()
                self._set_list_view(self.group_list_model)

    def _db_add_new(self):
        self._update_db(mode = 0)

    def _db_update_selected(self):
        if self.list_model_idx:
            id = self.list_model_idx.data(DataStoreRole)['id']
            self._update_db(mode = 1, id = id)

    def _db_del_selected(self):
        if self.comboBox_2.currentIndex() == 0:
            list_model = self.pattern_list_model
        elif self.comboBox_2.currentIndex() == 1:
            list_model = self.group_list_model

        for i in list_model.listdata:
            if i.checked:
                self._update_db(mode = 2, id = i.data['id'])

    def _update_list_models(self):
        pattern_data = []
        group_data = []
        for p in list(Pattern.select()):
            try:
                rule = json.loads(p.rule)
            except:
                rule = {}
            pattern_data.append({
                'text': p.name.decode('utf8'),
                'data': {
                    'id': p.id,
                    'rule': rule,
                    'mol': p.mol,
                    'polarity': p.polarity,
                    'minmz': p.minmz
                    }
                })

        self.pattern_list_model.set_data(pattern_data) #ListModel_1(pattern_data)
        #self.pattern_list_model.reset()

        for p in list(PeriphGroup.select()):
            group_data.append({
                'text': p.name.decode('utf8'),
                'data': {
                    'id': p.id,
                    'mol': p.mol,
                    'exact_mass': p.exact_mass
                    }
                })

        self.group_list_model.set_data(group_data) #ListModel_1(group_data)
        #self.group_list_model.reset()

    def _set_pattern_types(self):
        self.comboBox_2.clear()
        self.comboBox_2.addItems([i[0] for i in self.pattern_types])
        self.comboBox_2.setCurrentIndex(0)

    def _set_pattern_or_group_info(self, idx):
        self.list_model_idx = idx
        name = idx.data(QtCore.Qt.DisplayRole)
        info = idx.data(DataStoreRole)
        if self.comboBox_2.currentIndex() == 0:
            self.lineEdit_3.setText(str(info['minmz']))
            rule = info['rule']
            self.lineEdit_4.setText(name)
            if info['polarity'] == '+':
                self.radioButton_3.setChecked(True)
                self.radioButton_4.setChecked(False)
            elif info['polarity'] == '-':
                self.radioButton_3.setChecked(False)
                self.radioButton_4.setChecked(True)
            for k, v in rule.items():
                if self.pattern_plain_text_edits.has_key(k):
                    self.pattern_plain_text_edits[k].setPlainText(v)
        elif self.comboBox_2.currentIndex() == 1:
            self.lineEdit_5.setText(name)
            self.lineEdit_6.setText(str(info['exact_mass']))
        mol = info['mol'].replace('\r', '').replace('\n', '\\n')
        self.mol_editor.set_mol(mol)

    def _set_periph_emass(self, mol):
        if self.comboBox_2.currentIndex() == 1:
            self.lineEdit_6.setText(str(round(Mol2pic(mol).exact_mass, 5)))

    def _set_ms_info(self, idx):
        scan = idx.data(DataStoreRole)
        self._reset_polarity(scan.polarity)
        self.lineEdit_2.setText(str(round(scan.precursor_mz, 5)))
        self.plainTextEdit.setPlainText(
                '\n'.join(['%s    %s' %(i[0], i[1]/scan.base_peak_intensity*100) for i in
                    scan.mz_intensity_list if i[1]/scan.base_peak_intensity*100 >= self.settings.noise_level]))
        self._up_date_ms_viewer()

    def _clear_pattern_or_group_info(self, idx = None):
        if not idx:
            idx = self.comboBox_2.currentIndex()
        if idx == 0:
            self.lineEdit_3.setText('')
            self.lineEdit_4.setText('')
            for e in self.pattern_plain_text_edits.values():
                e.setPlainText('')
        elif idx == 1:
            self.lineEdit_5.setText('')
            self.lineEdit_6.setText('')
        self.mol_editor.set_mol('')

    def _on_combobox_2_index_changed(self, idx):
        # rules
        if idx == 0:
            self._clear_pattern_or_group_info(1)
            self._set_list_view(self.pattern_types[idx][1])
            self.groupBox.setEnabled(True)
            self.label_6.hide()
            self.label_8.hide()
            self.lineEdit_5.hide()
            self.lineEdit_6.hide()
            self.pushButton_9.hide()
            self.pushButton_7.show()
            self.pushButton_8.show()
            self.pushButton_11.show()
            self.pushButton_13.hide()
            self.pushButton_14.hide()
        # groups
        if idx == 1:
            self._clear_pattern_or_group_info(0)
            self._set_list_view(self.pattern_types[idx][1])
            self.groupBox.setEnabled(False)
            self.label_6.show()
            self.label_8.show()
            self.lineEdit_5.show()
            self.lineEdit_6.show()
            self.pushButton_9.show()
            self.pushButton_7.hide()
            self.pushButton_8.hide()
            self.pushButton_11.hide()
            self.pushButton_13.show()
            self.pushButton_14.show()

    def _update_ms_resolution(self, idx):
        self.ms_resolution = self.resolutions[idx][1]
        self._up_date_ms_viewer()

    def _on_list_view_del_selected(self):
        listdata = []
        if self.comboBox_2.currentIndex() == 0:
            listmodel = self.pattern_list_model
        elif self.comboBox_2.currentIndex() == 1:
            listmodel = self.group_list_model

        for item in listmodel.listdata:
            if not item.checked:
                listdata.append(item)
        listmodel.set_data(listdata)
        #listmodel.reset()
        if len(listmodel.listdata) == 0:
            self._clear_pattern_or_group_info()

    def _set_adduct_ions(self, checked = True):
        if not checked:
            return

        if self.radioButton.isChecked():
            self.ion_list = self.positive_ions
            self.polarity = '+'
        elif self.radioButton_2.isChecked():
            self.ion_list = self.negative_ions
            self.polarity = '-'

        self.comboBox.clear()
        self.comboBox.addItems([ i[1] for i in self.ion_list ])
        self.comboBox.setCurrentIndex(0)

    def _reset_polarity(self, pol = '+'):
        if pol == '+':
            self.radioButton.setChecked(True)
        else:
            self.radioButton_2.setChecked(True)

    def _on_combobox_index_changed(self, idx):
        if not self.orig_line_edit_text:
            self.orig_line_edit_text = self.lineEdit.text()

        if idx == self.comboBox.count() - 1:
            self.lineEdit.setEnabled(True)
            self.lineEdit.clear()
        else:
            self.lineEdit.setEnabled(False)
            self.lineEdit.setText(self.orig_line_edit_text)

    # MS related
    def _up_date_ms_viewer(self):
        self.ms_viewer.resolution = self.ms_resolution
        self.ms_viewer.set_ms(self.plainTextEdit.toPlainText().encode('utf8'))
        #open('a.svg', 'w').write(self.ms_viewer.svg)

    def _on_import_ms_file(self):
        file_name = self.file_selector.getOpenFileName(self,
            tr('Open MS data (text file)'),
            self.last_ms_file_dir,
            tr('*.txt (text file)(*.txt);;*.* (All types of file)(*)'))[0]
        if file_name != '' and os.path.exists(file_name):
            self.last_ms_file_dir = os.path.dirname(file_name)
            try:
                self.plainTextEdit.setPlainText(open(file_name, 'r').read())
                self._up_date_ms_viewer()
                self.statusbar.showMessage(tr('File "%s" successfully imported!') %file_name, 10000)
            except Exception, e:
                self._show_error(tr('Failed to import file: %s') %str(e).decode('utf8'))

    def _on_export_ms_file(self):
        file_name = self.file_selector.getSaveFileName(self,
            tr('Save MS data (text file)'),
            self.last_ms_file_dir,
            tr('*.txt (text file)(*.txt);;*.* (All types of file)(*)'))[0]
        if file_name != '':
            if not file_name.endswith('.txt'):
                file_name = file_name + '.txt'
            self.last_ms_file_dir = os.path.dirname(file_name)
            try:
                open(file_name, 'w').write(self.plainTextEdit.toPlainText().encode('utf8'))
                self.statusbar.showMessage(tr('File "%s" successfully exported!') %file_name, 10000)
            except Exception, e:
                self._show_error(tr('Failed to save file: %s') %str(e).decode('utf8'))

    def _on_open_mzxml_file(self):
        file_name = self.file_selector.getOpenFileName(self,
            tr('Open mzXML file'),
            self.last_mzxml_file_dir,
            tr('*.mzXML (mzXML file)(*.mzXML);;*.* (All types of file)(*)'))[0]
        if file_name != '' and os.path.exists(file_name):
            self.last_mzxml_file_dir = os.path.dirname(file_name)
            try:
                self.ms_list = parse_mzXML(file_name)
                tmp_ms2 = extract_MSn(self.ms_list, 2)
                if not tmp_ms2:
                    self._show_error(tr('''File "%s" dosen't contain any MS2 scan!''') %file_name)
                    return
                tmpl = [tmp_ms2[0]]
                for scan in tmp_ms2[1:]:
                    if abs(scan.precursor_mz - tmpl[-1].precursor_mz) < 0.01:
                        tmpl[-1] = tmpl[-1] + scan
                    else:
                        tmpl.append(scan)
                tmpl1 = []
                tmpl2 = []
                for  i in tmpl:
                    if i.filter_line.startswith('RT'):
                        tmpl1.append(i)
                    else:
                        tmpl2.append(i)

                self.ms2_list_model.set_data(tmpl1+tmpl2)
                self.statusbar.showMessage(tr('File "%s" successfully loaded!') %file_name, 10000)
            except Exception, e:
                self._show_error(tr('Failed to open file: %s') %str(e).decode('utf8'))

    def _on_dump_mzlst_file(self):
        if not self.ms2_list_model.listdata:
            self._show_error('Nothing to dump!')
            return
        file_name = self.file_selector.getSaveFileName(self,
            tr('Save results to file'),
            '%s' %self.last_mzlst_file_dir,
            tr('*.mzlst (Results mz list file)(*.mzlst);;*.* (All types of file)(*)'))[0]
        if file_name != '':
            if not file_name.endswith('.mzlst'):
                file_name += '.mzlst'
            self.last_mzlst_file_dir
            self.last_mzlst_file_dir = os.path.dirname(file_name)
            try:
                zipickle.dump(self.ms2_list_model.listdata, file_name, 1)
                self.statusbar.showMessage(tr('File "%s" successfully dumped!') %file_name, 10000)
            except Exception, e:
                print e
                self._show_error(tr('Failed to dump to file: %s') %str(e).decode('utf8'))

    def _on_load_mzlst_file(self):
        file_name = self.file_selector.getOpenFileName(self,
            tr('Load mzlst file'),
            '%s' %self.last_mzlst_file_dir,
            tr('*.mzlst (Results mz list file)(*.mzlst);;*.* (All types of file)(*)'))[0]
        if file_name != '':
            self.last_mzlst_file_dir
            self.last_mzlst_file_dir = os.path.dirname(file_name)
            try:
                self.query = 'Load from file "%s"' %file_name
                self.ms2_list_model.set_data(zipickle.load(file_name))
                if self.ms2_list_model.listdata:
                    self.listView_2.show()
                    self.pushButton_16.show()
                    self.statusbar.showMessage(tr('File "%s" successfully loaded!') %file_name, 10000)
                else:
                    self.statusbar.showMessage(tr('File "%s" contains no data!') %file_name, 10000)
                    self.listView_2.hide()
            except Exception, e:
                print e
                self._show_error(tr('Failed to load file: %s') %str(e).decode('utf8'))

    def _get_adduct(self):
        crtidx = self.comboBox.currentIndex()
        if crtidx == self.comboBox.count() - 1:
            return float(self.lineEdit.text())
        else:
            return float(self.ion_list[crtidx][0])

    def _query_periph_group(self, exact_mass):
        #print exact_mass
        if self.ms_resolution == 0:
            return PeriphGroup.select(abs(PeriphGroup.q.exact_mass - exact_mass) <= self.settings.iso_width)
        elif self.ms_resolution == 1:
            return PeriphGroup.select(abs(PeriphGroup.q.exact_mass - exact_mass)/exact_mass <= self.settings.deviation)

    def _analyze_ms(self):
        try:
            adduct = self._get_adduct()
            precursor = float(self.lineEdit_2.text())
        except:
            self._show_error(tr('Please fill the precursor and (or) adduct ion item(s).'))
            return []

        results = []

        if self.ms_viewer.ms_obj:
            self.table_overlay.resize(self.tableView.size())
            self.table_overlay.show()
            self.ms_viewer.ms_obj.set_precursor(precursor)
            self.ms_viewer.ms_obj.set_adduct(adduct)

        #pattern = open('lib/spectra/tst.txt').read()
        #print pattern #self.ms_viewer.ms_obj.analysis(pattern)
        for pattern in Pattern.select(AND(Pattern.q.polarity == self.polarity,
            precursor - adduct > Pattern.q.minmz)):
            skel = Skel()
            skels = []
            poses = []
            groups = []
            group_tmpd = {}
            info = 'Type: %s' %pattern.name
            for result in self.ms_viewer.ms_obj.analysis(pattern.rule):
                if result == {}:
                    results.append((pattern.mol, info))
                    continue
                for pos, exact_mass in result.items():
                    em = ''
                    g = []
                    if type(exact_mass) == types.FloatType or type(exact_mass) == types.IntType:
                        em = exact_mass
                        g = [ m.mol for m in list(self._query_periph_group(exact_mass)) ]
                    else:
                        if type(exact_mass) == types.ListType or type(exact_mass) == types.TupleType:
                            g = list(exact_mass)
                        else:
                            g = [exact_mass]
                    if g:
                        if not group_tmpd.has_key(pos):
                            group_tmpd[pos] = g
                        else:
                            group_tmpd[pos] += g
                    #print group_tmpd
                    if em:
                        info += '\n%s: %.4f' %(pos, em)

            for k, v in group_tmpd.items():
                poses.append(k)
                groups.append(v)

            all_products = list(myproduct(groups))
            skel.set_skel(Mol(pattern.mol).get_skel())
            all_skels = []

            for idx, p in enumerate(poses):
                if not all_skels:
                    for group in all_products:
                        all_skels += skel.link_skel(Mol(group[idx]).get_skel(), p)
                        #print 'len all_skels: ', len(all_skels)
                else:
                    for group in all_products:
                        rm_indexes = []
                        all_sk = []
                        gsk = Mol(group[idx]).get_skel()
                        for idx0, sk in enumerate(all_skels):
                            sks = []
                            skel.set_skel(sk)
                            sks = skel.link_skel(gsk, p)
                            if sks:
                                rm_indexes.append(idx0)
                                all_sk += sks
                        tpl = []
                        for i, s in enumerate(all_skels):
                            if not i in rm_indexes:
                                tpl.append(s)
                        all_skels = tpl + all_sk

            for sk in all_skels:
                results.append((Mol2pic(skel.set_skel(sk).get_mol()).to_mol(auto_layout = True), info))

        tpl = []
        pre = random.randint(10000, 99999)
        for i, r in enumerate(results):
            tpl.append(['%s-%s' %(pre, i+1),
                Mol2pic(r[0]).to_pic(),
                r[1],
                r[0],
                ''])
        self.table_model.arraydata = tpl
        self._reset_table_model()
        self.table_overlay.hide()

    def _confirm_result(self, id, mol):
        if self.sdf_viewer.tabWidget:
            for i in range(self.sdf_viewer.tabWidget.count()):
                if self.sdf_viewer.tabWidget.tabText(i) == id:
                    self.tabWidget.setCurrentIndex(2)
                    self.sdf_viewer.tabWidget.setCurrentIndex(i)
                    return
        query = ''
        if id and mol:
            info = {'id': id,
                    'mol': mol,
                    'ms_obj': self.ms_viewer.ms_obj,
                    'num_results': self.settings.num_confirm_results,
                    'charge': 0,
                    }
            query = self.gen_confirm_query(info)
            #return

        if query:
            self.table_overlay.show()
            self.molconfirmer.set_query(query)
            self.molconfirmer.start()

    def _sdf_viewer_set_confirm_results(self, results):
        self.table_overlay.hide()
        if results.has_key('sdf'):
            if not '> <' in results['sdf']:
                self.sdf_viewer.add_view('_blank')
            else:
                filter = lambda mol: sub_filter(results['mol'], mol)
                filtered_results = filter_sdf(results['sdf'], [filter])
                num_all = filtered_results.setdefault('num_all', 0)
                num_hits = filtered_results.setdefault('num_hits', 0)
                sdf = filtered_results.setdefault('sdf', '')
                if self.sdf_viewer.view.sdf_string != '':
                    self.sdf_viewer.add_view()
                self.sdf_viewer.set_data(sdf)
                self.statusbar.showMessage(tr('%s canditates filtered, %s hits obtained.') %(num_all, num_hits))
            self.sdf_viewer.set_title(results['id'])
            self.tabWidget.setCurrentIndex(2)

    def _save_to_mol_file(self, contents):
        file_name = self.file_selector.getSaveFileName(self,
            tr('Save to MOL file'),
            self.last_mol_file_dir,
            tr('*.mol (MDL/MOL file)(*.mol);;*.* (All types of file)(*)'))[0]
        if file_name != '':
            if not file_name.endswith('.mol'):
                file_name = file_name + '.mol'
            self.last_mol_file_dir = os.path.dirname(file_name)
            try:
                open(file_name, 'w').write(contents)
            except Exception, e:
                self._show_error(tr('Failed to save file: %s') %str(e).decode('utf8'))

    def _on_tableView_clicked(self, qidx):
        if qidx.column() == len(self.table_header) - 1:
            self._save_to_mol_file(self.table_model.arraydata[qidx.row()][-1])
        elif qidx.column() == len(self.table_header) - 2:
            self._confirm_result(self.table_model.arraydata[qidx.row()][0],
                    self.table_model.arraydata[qidx.row()][-2])
        elif qidx.column() == 1:
            svg = self.table_model.arraydata[qidx.row()][qidx.column()]
            sv = SvgViewer(svg)
            if not sv in self.svs:
                self.svs.append(sv)
                self.svs[-1].setWindowTitle(tr('Structure Viewer'))
                self.svs[-1].show()
            # delete closed structure viewer instances
            for w in self.svs:
                if not w.isVisible():
                    self.svs.pop(self.svs.index(w))
